package com.example.android.photobyintent;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.ImageView;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import org.opencv.android.OpenCVLoader;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.Size;
import org.opencv.features2d.DescriptorExtractor;
import org.opencv.features2d.FeatureDetector;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import android.widget.ProgressBar;
import android.widget.TextView;

import static android.view.View.VISIBLE;

public class PhotoIntentActivity extends Activity {

	private static final int ACTION_TAKE_PHOTO = 1;
	private static final int ACTION_SELECT_PHOTO = 2;
    private static final int ACTION_UPLOAD_FILE = 3;

	private ImageView mImageView;
    private TextView mTxtProcessPercentage;

	private String mCurrentPhotoPath = null;
    private String mFeatureFilePath = null;

	private static final String JPEG_FILE_PREFIX = "IMG-";
	private static final String JPEG_FILE_SUFFIX = ".jpg";

	private AlbumStorageDirFactory mAlbumStorageDirFactory = null;

	private static String mLogTag = "YANCHAO";

    // for uploading file
    private String mUploadFilePath = null;
    private ProgressBar mProgressBar;
    long mTotalSize = 0;

    // for OpenCV
    static {
        if(!OpenCVLoader.initDebug()){
            Log.i(mLogTag, "OpenCV load fail.");
        } else {
            Log.i(mLogTag, "OpenCV Load okay");
        }
    }

	/* Photo album for this application */
	private String getAlbumName() {
		return getString(R.string.albumName);
	}

	private File getAlbumDir() {
		File storageDir = null;
		if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
			storageDir = mAlbumStorageDirFactory.getAlbumStorageDir(getAlbumName());
			if (storageDir != null) {
				if (! storageDir.mkdirs()) {
					if (! storageDir.exists()){
						Log.i("CameraSample", "failed to create directory");
						return null;
					}
				}
			}
		} else {
			Log.i(mLogTag, "External storage is not mounted READ/WRITE.");
		}
		return storageDir;
	}

	private File createImageFile() throws IOException {
		// Create an image file name
		String timeStamp = new SimpleDateFormat(getString(R.string.img_data_format)).format(new Date());
		String imageFileName = JPEG_FILE_PREFIX + timeStamp + "-";
		File albumF = getAlbumDir();
		File imageF = File.createTempFile(imageFileName, JPEG_FILE_SUFFIX, albumF);
		return imageF;
	}

    // generate image file and return it
	private File setUpPhotoFile() throws IOException {
		File f = createImageFile();
		mCurrentPhotoPath = f.getAbsolutePath();
		Log.i(mLogTag, "Current Photo Path: " + mCurrentPhotoPath);
		return f;
	}

	private void setViewPic() {
		/* There isn't enough memory to open up more than a couple camera photos */
		/* So pre-scale the target bitmap into which the file is decoded */

		/* Get the size of the ImageView */
		int targetW = mImageView.getWidth();
		int targetH = mImageView.getHeight();
		Log.i(mLogTag, "image view width and height:" + targetW + "/" + targetH);

		/* Get the size of the image */
		BitmapFactory.Options bmOptions = new BitmapFactory.Options();
		bmOptions.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
		int photoW = bmOptions.outWidth;
		int photoH = bmOptions.outHeight;
		Log.i(mLogTag, "image photo width and height:" + photoW + "/" + photoH);

		/* Figure out which way needs to be reduced less */
		int scaleFactor = 1;
		if ((targetW > 0) || (targetH > 0)) {
			scaleFactor = Math.min(photoW/targetW, photoH/targetH);
		}

		/* Set bitmap options to scale the image decode target */
		bmOptions.inJustDecodeBounds = false;
		bmOptions.inSampleSize = scaleFactor;

		/* Decode the JPEG file into a Bitmap */
		Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
		//Bitmap bitmap = BitmapFactory.decodeFile("/storage/emulated/0/Pictures/MyPictures/south-build.jpg", bmOptions);

		/* Associate the Bitmap to the ImageView */
		mImageView.setImageBitmap(bitmap);
        mImageView.setVisibility(VISIBLE);
	}

	private void galleryAddPic() {
        Intent mediaScanIntent = new Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE");
        File f = new File(mCurrentPhotoPath);
        Uri contentUri = Uri.fromFile(f);
        mediaScanIntent.setData(contentUri);
        this.sendBroadcast(mediaScanIntent);
        Log.i(mLogTag, "gallery add picture " + mCurrentPhotoPath);
	}

	private void dispatchTakePictureIntent() {
		Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        File f = null;
        mCurrentPhotoPath = null;
        try {
            f = setUpPhotoFile();
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
        } catch (IOException e) {
            e.printStackTrace();
            mCurrentPhotoPath = null;
        }
        Log.i(mLogTag, "dispatchTakePictureIntent: ACTION_TAKE_PHOTO: " + mCurrentPhotoPath);
		startActivityForResult(takePictureIntent, ACTION_TAKE_PHOTO);
	}

	private void dispatchSelectPictureIntent() {
		Intent selectPhotoIntent = new Intent(Intent.ACTION_GET_CONTENT);
		selectPhotoIntent.setType( "image/*");
        startActivityForResult(Intent.createChooser(selectPhotoIntent, "Select Image"), ACTION_SELECT_PHOTO);
	}

    private void handleTakePhoto() {
		if (mCurrentPhotoPath != null) {
			galleryAddPic();
            setViewPic();
		}
	}

    @SuppressWarnings("deprecation")
	private void handleSelectPhoto(Intent intent) {
        mCurrentPhotoPath = null;
        Uri uri = intent.getData();
        String[] proj = {MediaStore.Images.Media.DATA};
        Cursor cursor = managedQuery(uri, proj, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        mCurrentPhotoPath = cursor.getString(column_index);
        Log.i(mLogTag, "handleSelectPhoto selected file: " + mCurrentPhotoPath);

        if(mCurrentPhotoPath != null) {
            setViewPic();
            // extract feature using OpenCV
            ExtractFeature();
        } else{
            Log.w(mLogTag, "handleSelectPhoto selected file in null");
        }
	}

	Button.OnClickListener mTakePicOnClickListener =
		new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                dispatchTakePictureIntent();
            }
        };

	Button.OnClickListener mSelectPicOnClickListener =
		new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                dispatchSelectPictureIntent();
            }
        };

    Button.OnClickListener mUploadFileOnClickListener =
        new Button.OnClickListener(){
            @Override
            public void onClick(View v){
                mUploadFilePath = mCurrentPhotoPath;
                Log.i(mLogTag, "mUploadFileOnClickListener: uploadFile " + mUploadFilePath);
                new UploadFileToServer().execute();
            }
        };

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		mImageView = (ImageView) findViewById(R.id.imageView);
        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
        mTxtProcessPercentage = (TextView) findViewById(R.id.txtProcessPercentage);

		mLogTag = getString(R.string.app_name);
        Log.i(mLogTag, "onCreate()");

		Button mBtnPicTake = (Button) findViewById(R.id.btnIntendTake);
        Button mBtnPicSelect = (Button) findViewById(R.id.btnIntendSelect);
        Button mBtnFileUpload = (Button) findViewById(R.id.btnUploadFile);

		setBtnListenerOrDisable(mBtnPicTake, mTakePicOnClickListener, MediaStore.ACTION_IMAGE_CAPTURE);
        setBtnListenerOrDisable(mBtnPicSelect, mSelectPicOnClickListener, MediaStore.ACTION_IMAGE_CAPTURE);
        mBtnFileUpload.setOnClickListener(mUploadFileOnClickListener);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
			mAlbumStorageDirFactory = new FroyoAlbumDirFactory();
            Log.i(mLogTag, "Album Storage Dir Factory: Froyo Album");
		} else {
			mAlbumStorageDirFactory = new BaseAlbumDirFactory();
            Log.i(mLogTag, "Album Storage Dir Factory: Base Album");
		}
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
		switch (requestCode) {
            case ACTION_TAKE_PHOTO: {
                Log.i(mLogTag, "ACTION_TAKE_PHOTO");
                if (resultCode == RESULT_OK) {
                    handleTakePhoto();
                }
                break;
            } // ACTION_TAKE_PHOTO

            case ACTION_SELECT_PHOTO: {
                Log.i(mLogTag, "ACTION_SELECT_PHOTO");
                if (resultCode == RESULT_OK) {
                    handleSelectPhoto(data);
                }
                break;
            } // ACTION_SELECT_PHOTO

            case ACTION_UPLOAD_FILE:{
                Log.i(mLogTag, "ACTION_UPLOAD_FILE");
                break;
            } // ACTION_UPLOAD_FILE
		} // switch
	}

	/**
	 * Indicates whether the specified action can be used as an intent. This
	 * method queries the package manager for installed packages that can
	 * respond to an intent with the specified action. If no suitable package is
	 * found, this method returns false.
	 * http://android-developers.blogspot.com/2009/01/can-i-use-this-intent.html
	 *
	 * @param context The application's environment.
	 * @param action The Intent action to check for availability.
	 *
	 * @return True if an Intent with the specified action can be sent and
	 *         responded to, false otherwise.
	 */
	public static boolean isIntentAvailable(Context context, String action) {
		final PackageManager packageManager = context.getPackageManager();
		final Intent intent = new Intent(action);
		List<ResolveInfo> list =
			packageManager.queryIntentActivities(intent,
					PackageManager.MATCH_DEFAULT_ONLY);
		return list.size() > 0;
	}

	private void setBtnListenerOrDisable( 
			Button btn, 
			Button.OnClickListener onClickListener,
			String intentName
	) {
		if (isIntentAvailable(this, intentName)) {
			btn.setOnClickListener(onClickListener);        	
		} else {
			btn.setText( 
				getText(R.string.cannot).toString() + " " + btn.getText());
			btn.setClickable(false);
		}
	}

	/**
	 * Uploading the file to server
	 * */
	private class UploadFileToServer extends AsyncTask<Void, Integer, String> {
		@Override
		protected void onPreExecute() {
			// setting progress bar to zero
            mProgressBar.setProgress(0);
			super.onPreExecute();
		}

		@Override
		protected void onProgressUpdate(Integer... progress) {
			// Making progress bar visible
            mProgressBar.setVisibility(VISIBLE);

			// updating progress bar value
            mProgressBar.setProgress(progress[0]);

			// updating percentage value
			mTxtProcessPercentage.setText(String.valueOf(progress[0]) + "%");
		}

		@Override
		protected String doInBackground(Void... params) {
			return uploadFile();
		}

		@SuppressWarnings("deprecation")
		private String uploadFile() {
			String responseString;
			HttpClient httpclient = new DefaultHttpClient();
			HttpPost httppost = new HttpPost(getString(R.string.uploadURL));
            Log.i(mLogTag, "UploadFileToServer: uploadFile " + mUploadFilePath);

			try {
				AndroidMultiPartEntity entity = new AndroidMultiPartEntity(
                    new AndroidMultiPartEntity.ProgressListener() {

                        @Override
                        public void transferred(long num) {
                            publishProgress((int) ((num / (float) mTotalSize) * 100));
                        }
                    });

				File sourceFile = new File(mUploadFilePath);

				// Adding file data to http body
				entity.addPart("image", new FileBody(sourceFile));

				// Extra parameters if you want to pass to server
				// entity.addPart("website", new StringBody("www.androidhive.info"));
				// entity.addPart("email", new StringBody("abc@gmail.com"));

				mTotalSize = entity.getContentLength();
				httppost.setEntity(entity);

				// Making server call
				HttpResponse response = httpclient.execute(httppost);
				HttpEntity r_entity = response.getEntity();

				int statusCode = response.getStatusLine().getStatusCode();
                Log.i(mLogTag, "http response status code is " + statusCode );
				if (statusCode == 200) {
					// Server response
					responseString = EntityUtils.toString(r_entity);
				} else {
					responseString = "Error occurred! Http Status Code: " + statusCode;
				}
			} catch (ClientProtocolException e) {
				responseString = e.toString();
			} catch (IOException e) {
				responseString = e.toString();
			}
            mUploadFilePath = null;
			return responseString;
		}

		@Override
		protected void onPostExecute(String result) {
			Log.e(mLogTag, "Response from server: " + result);
			// showing the server response in an alert dialog
			showAlert(result);
			super.onPostExecute(result);
		}

	}

	/**
	 * Method to show alert dialog
	 * */
	private void showAlert(String message) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setMessage(message).setTitle("Response from Servers")
				.setCancelable(false)
				.setPositiveButton("OK", new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						// do nothing
					}
				});
		AlertDialog alert = builder.create();
		alert.show();
	}


    /**
    *  Extract BRISK feature using OpenCV
    * */
    private boolean ExtractFeature() {
        FeatureDetector detector = FeatureDetector.create(FeatureDetector.BRISK);
        DescriptorExtractor descriptor = DescriptorExtractor.create(DescriptorExtractor.BRISK);

        Log.i(mLogTag, "ExtractFeature file: " + mCurrentPhotoPath);
        if(mCurrentPhotoPath==null){
            Log.i(mLogTag, "ExtractFeature mCurrentPhotoPath is null");
            return false;
        }

        Mat img = Imgcodecs.imread(mCurrentPhotoPath, Imgcodecs.IMREAD_GRAYSCALE);
        if(img.empty()){
            Log.i(mLogTag, "ExtractFeature image mat is empty");
            return false;
        }

        Log.i(mLogTag, "ExtractFeature image size: " + img.rows() + " / " + img.cols());

        // Down sample Image
        int scaleFactor = 1;
        int imgW = img.cols();
        int imgH = img.rows();
        int imgSize = 1000;
        if ((imgW > 0) || (imgH > 0)) {
            scaleFactor = Math.min(imgW/imgSize, imgH/imgSize);
        }

        Size sz = img.size();
        sz.height = Math.round(imgH/scaleFactor);
        sz.width = Math.round(imgW/scaleFactor);
        Mat imgSmall = new Mat(sz, CvType.CV_8U);
        Log.i(mLogTag, "ExtractFeature imgSmall size: " + imgSmall.rows() + " / " + imgSmall.cols());

        Imgproc.resize(img, imgSmall, imgSmall.size(), 0, 0, Imgproc.INTER_LINEAR);

        Mat desc = new Mat();
        MatOfKeyPoint keypt = new MatOfKeyPoint();
        detector.detect(imgSmall, keypt);
        descriptor.compute(imgSmall, keypt, desc);

        Log.i(mLogTag, "ExtractFeature key point size:" + keypt.rows());


        return true;
    }
}